package com.company.finance.ddd.domain.core.voucher.repository;

import com.company.finance.ddd.common.IRepository;
import com.company.finance.ddd.domain.core.voucher.aggregate.Voucher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;

import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author wanglei
 * @version $$Id: AccountingDocumentRepository, v 0.1 2021/2/5 15:10 wanglei Exp $$
 */
@Repository
public class VoucherRepository implements IRepository<Voucher> {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void save(Voucher voucher) {
        assert null != voucher;
        if (null != voucher.getID()) {
            jdbcTemplate.update("update accounting_document set state = ?, send_time = ?, remark = ? where id = ?"
                    , voucher.getSTATE(), voucher.getSEND_TIME(), voucher.getREMARK(), voucher.getID());
            return;
        }
        saveEntity(voucher);
        if (null != voucher.getItems() && !voucher.getItems().isEmpty()) {
            //TODO Replace this method by VoucherItemRepository
            saveItem(voucher);
        }
    }

    private Map<Integer, String> columnsCache = new HashMap<>();

    private void saveEntity(Voucher voucher) {
        StringBuilder columns = new StringBuilder();
        StringBuilder values = new StringBuilder();

        List<Object> params = new ArrayList<>();
        try {
            for (Field field : voucher.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                Object fieldValue = field.get(voucher);
                if (null != fieldValue && !(fieldValue instanceof List)) {
                    columns.append(field.getName()).append(", ");
                    values.append("?").append(", ");
                    params.add(fieldValue);
                }
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        String sql = MessageFormat.format("insert into accounting_document ({0}) values ({1})"
                , columns.delete(columns.length() - 2, columns.length())
                , values.delete(values.length() - 2, values.length()));
        PreparedStatementCreatorFactory preparedStatementCreatorFactory = new PreparedStatementCreatorFactory(sql);
        preparedStatementCreatorFactory.setReturnGeneratedKeys(true);
        for (int i = 0; i < params.size(); i++) {
            preparedStatementCreatorFactory.addParameter(new SqlParameter(1));
        }
        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(preparedStatementCreatorFactory.newPreparedStatementCreator(sql, params.toArray()), keyHolder);
        if (null == keyHolder.getKey()) {
            throw new RuntimeException("Generate primary key exception.");
        }
       voucher.setID(keyHolder.getKey().longValue());
    }

    @Override
    public List<Voucher> query(Map<String, Object> queryCondition) {
        Object[] param = {queryCondition.get("bill_id")};
        return jdbcTemplate.query("select * from accounting_document where bill_id = ?"
                , param, new BeanPropertyRowMapper<>(Voucher.class));
    }

    @Override
    public Voucher get(Long voucherId) {
        Long[] param = {voucherId};
        String sql = "select * from accounting_document where id = ?";
        Voucher voucher = jdbcTemplate.queryForObject(sql, param, new BeanPropertyRowMapper<>(Voucher.class));
        if (null == voucher) {
            return null;
        }
        sql = "select * from accounting_document_item where id = ?";
        voucher.setItems(jdbcTemplate.query(sql, param, new BeanPropertyRowMapper<>(Voucher.VoucherItem.class)));
        return voucher;
    }

    @Override
    public void delete(Long billId) {
        Long[] param = {billId};
        jdbcTemplate.queryForList("select id from accounting_document where bill_id = ?", param, Long.class)
                .forEach(voucherId -> {
                    jdbcTemplate.update("delete from accounting_document where bill_id = ?", billId);
                    jdbcTemplate.update("delete from accounting_document_item where voucher_id = ?", voucherId);
                });
    }

    private void saveItem(Voucher voucher) {
        String sql = "insert into accounting_document_item ({0}) values ({1})";

        voucher.getItems().forEach(voucherItem -> {
            List<Object[]> batchParams = new ArrayList<>();

            voucherItem.setVOUCHER_ID(voucher.getID());

            StringBuilder columns = new StringBuilder();
            StringBuilder valuePlaceholder = new StringBuilder();
            //Build sql column and placeholder
            List<Object> params = new ArrayList<>();
            for (Field field : voucherItem.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                Object fieldValue;
                try {
                    fieldValue = field.get(voucherItem);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
                if (null != fieldValue) {
                    columns.append(field.getName()).append(", ");
                    valuePlaceholder.append("?").append(", ");
                    params.add(fieldValue);
                }
            }
            batchParams.add(params.toArray());
            jdbcTemplate.batchUpdate(MessageFormat.format(sql, columns.delete(columns.length() - 2, columns.length())
                    , valuePlaceholder.delete(valuePlaceholder.length() - 2, valuePlaceholder.length())), batchParams);
        });
    }
}
